/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_register.h"
#include <stddef.h>
#include <string.h>
#include "securec.h"
#include "cJSON.h"
#include "ola_log.h"
#include "ola_error.h"
#include "ola_sdk_api.h"
#include "ola_cloud.h"
#include "ola_def.h"
#include "ola_base_utils.h"
#include "ola_link.h"
#include "ola_device_model.h"
#include "ola_data.h"
#include "ola_sdk.h"

/* 内存中保存的注册码有效时间为1min,根据需要设置，0表示不设置，单位为ms */
#define REGISTRY_CODE_VALID_TIME (1 * 60 * 1000)

typedef enum {
    REGISTER_STATE_SEND_REQUEST,
    REGISTER_STATE_WAIT_RESPONSE,
    REGISTER_STATE_FINISH,
    REGISTER_STATE_FAILED,
} RegisterState;

typedef struct {
    const char *name;
    const char *val;
} OlaDevNameValInfo;

static RegisterState g_registerState = REGISTER_STATE_SEND_REQUEST;
static unsigned long long g_registerTime = 0;

/* 注册码及有效时间全局变量 */
static unsigned long long g_registryCodeTime = 0;
static unsigned char g_registerCode[OLA_DEV_REGISTRY_CODE_LEN + 1] = {0};

static RegisterState OlaGetRegisterState(void)
{
    return g_registerState;
}

static void OlaSetRegisterState(RegisterState state)
{
    OlaLogInfo("set register state [%d]", state);
    g_registerState = state;
}

static int OlaAddDevinfoToJson(cJSON *json)
{
    const OlaDeviceInfo *devInfo = OlaGetDevInfo();
    if (devInfo == NULL) {
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    cJSON *devInfoJson = cJSON_CreateObject();
    if (devInfoJson == NULL) {
        OlaLogError("create json error");
        return OLA_CJSON_ERR_CREATE_OBJECT;
    }
    OlaDevNameValInfo devNameValInfo[] = {{OLA_STRING_UDID, devInfo->udid},
                                          {OLA_STRING_SN, devInfo->sn},
                                          {OLA_STRING_MODEL, devInfo->model},
#ifdef OLA_DEBUG
                                          {OLA_STRING_DEV_TYPE, "01D" },
                                          {OLA_STRING_MANU, "001" },
#else
                                          {OLA_STRING_CAT_ID, devInfo->catID},
                                          {OLA_STRING_VENDOR_ID, devInfo->vendorID},
#endif

                                          {OLA_STRING_DEVICENAME, devInfo->devName},
                                          {OLA_STRING_PRODUCTID, devInfo->prodId},
                                          {OLA_STRING_VERSION, devInfo->version},
                                          {OLA_STRING_MAC, devInfo->mac},
                                          {OLA_STRING_FWV, devInfo->fwv},
                                          {OLA_STRING_HWV, devInfo->hwv},
                                          {OLA_STRING_SWV, devInfo->swv}};

    for (int i = 0; i < sizeof(devNameValInfo) / sizeof(devNameValInfo[0]); i++) {
        if ((devNameValInfo[i].val == NULL) ||
            (cJSON_AddStringToObject(devInfoJson, devNameValInfo[i].name, devNameValInfo[i].val) == NULL)) {
            OlaLogError("add string error, name:[%s]", devNameValInfo[i].name);
            return OLA_CJSON_ERR_ADD_STRING;
        }
    }

    if (cJSON_AddNumberToObject(devInfoJson, OLA_STRING_PROT_TYPE, devInfo->protType) == NULL) {
        OlaLogError("add number error");
        return OLA_CJSON_ERR_ADD_NUMBER;
    }
    cJSON_AddItemToObjectCS(json, OLA_STRING_DEV_INFO, devInfoJson);
    return OLA_OK;
}

static int OlaParseRegisterErrCode(cJSON *json)
{
    cJSON *errCode = cJSON_GetObjectItem(json, OLA_STRING_ERRCODE);
    if (errCode == NULL) {
        OlaLogError("no errCode");
        return OLA_CJSON_ERR_PARSE;
    }

    if (errCode->valueint == OLA_OK) {
        return OLA_OK;
    }

    OlaLogError("parse register errcode:%d", errCode->valueint);

    switch (errCode->valueint) {
        case OLA_REGISTER_ERR: /* 设备注册失败 */
            return OLA_REGISTER_ERR;
        default:
            return OLA_REGISTER_ERR_OTHERS;
    }

    return OLA_OK;
}

static int OlaParseDevIdAndsecretCode(cJSON *json)
{
    char *devid = OlaGetJsonString(json, OLA_STRING_DEVID);
    if ((devid == NULL) || (strlen(devid) == 0) || (strlen(devid) > OLA_DEV_REGISTER_DEVID_LEN)) {
        OlaLogError("json parse devid err");
        return OLA_CJSON_ERR_GET_STRING;
    }

    OlaData devidData = { NULL, strlen(devid) };
    devidData.data = (unsigned char *)OlaMallocAndCopyStr(devid, strlen(devid));
    if ((devidData.data == NULL) || (OlaSetData(OLA_DATA_TYPE_DEV_ID, &devidData) != OLA_OK)) {
        OlaLogError("set cloud devid data failed");
        OlaDataFree(&devidData);
        return OLA_DATA_ERR_SET_DATA;
    }
    OlaDataFree(&devidData);

    /* 保存云端为应用终端分配的登录密码 */
    char *secret = OlaGetJsonString(json, OLA_STRING_SECRET);
    if ((secret == NULL) || (strlen(secret) == 0) || (strlen(secret) > OLA_DEV_REGISTER_SECRET_LEN)) {
        OlaLogError("json parse secret err");
        return OLA_CJSON_ERR_GET_STRING;
    }

    OlaData secretData = { NULL, strlen(secret) };
    secretData.data = (unsigned char *)OlaMallocAndCopyStr(secret, strlen(secret));
    if ((secretData.data == NULL) || (OlaSetData(OLA_DATA_TYPE_SECRET, &secretData) != OLA_OK)) {
        OlaLogError("set cloud secret data failed");
        OlaDataFree(&secretData);
        return OLA_DATA_ERR_SET_DATA;
    }
    OlaDataFree(&secretData);

    return OLA_OK;
}

static int OlaProcDeviceRegisterResp(const OlaData *payload)
{
    cJSON *json = cJSON_Parse((const char *)payload->data);
    if (json == NULL) {
        OlaLogError("json parse err");
        return OLA_CJSON_ERR_PARSE;
    }

    int ret = OlaParseRegisterErrCode(json);
    if (ret != OLA_OK) {
        OlaLogError("parse errCode err");
        goto FAILED;
    }

    ret = OlaParseDevIdAndsecretCode(json);
    if (ret != OLA_OK) {
        OlaLogError("parse device and secret err");
        goto FAILED;
    }
    cJSON_Delete(json);
    return OLA_OK;
FAILED:
    cJSON_Delete(json);
    return ret;
}

static void OlaDeviceRegisterRspHandler(OlaCoapSession *session, const OlaCoapPacket *pkt)
{
    if ((session == NULL) || (pkt == NULL) || !OlaIsValidCloudSession(session)) {
        OlaLogWarning("invalid param");
        return;
    }

    if (OlaGetRegisterState() != REGISTER_STATE_WAIT_RESPONSE) {
        OlaLogWarning("response in invalid state");
        return;
    }

    if (pkt->header.code != OLA_COAP_RESPONSE_CODE_CONTENT) {
        OlaLogWarning("coap response err[%d]", pkt->header.code);
        goto FAILED;
    }

    if ((pkt->payload.data == NULL) || (pkt->payload.len == 0)) {
        OlaLogWarning("empty response");
        goto FAILED;
    }

    if (OlaProcDeviceRegisterResp(&pkt->payload) != OLA_OK) {
        OlaLogWarning("parse dev register resp err");
        goto FAILED;
    }
    OlaSetRegisterState(REGISTER_STATE_FINISH);
    return;
FAILED:
    OlaSetRegisterState(REGISTER_STATE_FAILED);
}

static bool OlaWaitRegistryCodeOvertime(unsigned long long *startTime)
{
    bool isOvertime = false;
    int ret;
    if (*startTime == 0) {
        ret = OlaGetTime(startTime);
        if ((ret != 0) || (*startTime == 0)) {
            OlaLogError("get time error[%d]", ret);
            return false;
        }
    }

    ret = OlaIsOvertime(&isOvertime, *startTime, REGISTRY_CODE_VALID_TIME);
    if (ret != OLA_OK) {
        OlaLogError("check overtime error");
        return false;
    }
    if (isOvertime) {
        OlaLogWarning("wait register resp overtime");
    }
    return isOvertime;
}

int OlaRegisterSetRegistryCode(const unsigned char *registryCode)
{
    if ((registryCode == NULL) || (strlen((char *)registryCode) == 0) ||
        (strlen((char *)registryCode) > OLA_DEV_REGISTRY_CODE_LEN)) {
        OlaLogWarning("invaild registry code");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    if (strcpy_s((char *)g_registerCode, sizeof(g_registerCode), (char *)registryCode) != EOK) {
        OlaLogError("strcpy err");
        return OLA_SECUREC_ERR_STRCPY;
    }

    if (REGISTRY_CODE_VALID_TIME == 0) {
        OlaLogWarning("not set registry code timer");
        return OLA_OK;
    }
    /* 设置注册码开始时间 */
    g_registryCodeTime = 0;
    int errcode = OlaWaitRegistryCodeOvertime(&g_registryCodeTime);
    if (errcode != OLA_OK) {
        OlaLogWarning("set registry code time error");
        return OLA_REGISTER_ERR_REGISTRY_CODE_TIMEOUT;
    }
    return OLA_OK;
}

static bool OlaRegisterIsRegistryCodeValid(void)
{
    if (strlen((char *)g_registerCode) == 0) {
        OlaLogWarning("invaild registry code");
        return false;
    }

    if (REGISTRY_CODE_VALID_TIME == 0) {
        OlaLogWarning("not set registry code timer");
        return true;
    }

    if (g_registryCodeTime == 0) {
        OlaLogWarning("invaild registry code");
        return false;
    }

    /* 内存中保存的注册码从保存开始有效时间 */
    int errcode = OlaWaitRegistryCodeOvertime(&g_registryCodeTime);
    if (errcode != OLA_OK) {
        OlaLogWarning("wait registry code over time");
        return false;
    }
    return true;
}

static int OlaRegisterGetRegistryCode(OlaData *registry)
{
    /* 如果内存中保存的注册码有效，则优先使用内存中的注册码 */
    if (OlaRegisterIsRegistryCodeValid()) {
        registry->data = g_registerCode;
        registry->len = strlen((char *)g_registerCode);
        OlaLogInfo("get registry from memory");
        return OLA_OK;
    }

    /* 内存中保存的注册码无效，从安全存储读取注册码 */
    if (OlaGetData(OLA_DATA_TYPE_REGISTER_CODE, registry) != OLA_OK) {
        OlaLogError("get registry error");
        return OLA_DATA_ERR_GET_DATA;
    }
    OlaLogInfo("get registry from flash");
    return OLA_OK;
}

static int OlaAddTicketAndRegistryInfoToJson(cJSON *json)
{
    OlaData ticket = { NULL, OLA_DEV_TICKET_LEN };
    if (OlaGetData(OLA_DATA_TYPE_TICKET, &ticket) != OLA_OK) {
        OlaLogError("get ticket error");
        return OLA_DATA_ERR_GET_DATA;
    }

    if ((ticket.data == NULL) || (strlen((char *)ticket.data) == 0) ||
        (strlen((char *)ticket.data) > OLA_DEV_TICKET_LEN)) {
        OlaLogError("ticket len error");
        return OLA_DATA_ERR_GET_DATA;
    }

    OlaData registry = { NULL, OLA_DEV_REGISTRY_CODE_LEN };
    if (OlaRegisterGetRegistryCode(&registry) != OLA_OK) {
        OlaLogError("get registry error");
        return OLA_DATA_ERR_GET_DATA;
    }

    if ((registry.data == NULL) || (strlen((char *)registry.data) == 0) ||
        (strlen((char *)registry.data) > OLA_DEV_REGISTRY_CODE_LEN)) {
        OlaLogError("registry len error");
        return OLA_DATA_ERR_GET_DATA;
    }

    if ((cJSON_AddStringToObject(json, OLA_STRING_TICKET, (char *)ticket.data) == NULL) ||
        (cJSON_AddStringToObject(json, OLA_STRING_REGISTRY_CODE, (char *)registry.data) == NULL)) {
        OlaLogError("add string error");
        return OLA_CJSON_ERR_ADD_STRING;
    }

    return OLA_OK;
}
/*
 * RegisterReq参数定义:
 * 01    ticket         设备身份认证成功后，云端为应用终端颁发的身份凭据
 * 02    registryCode   配网阶段下发的绑定码
 * 03    udid           应用终端本身唯一标识，生成规则sha256(manu+model+sn)
 * 04    sn
 * 应用终端的序列号，用于唯一标识该应用终端；各厂商以及各应用终端可以根据MAC地址或者IMEI号等信息来填充；sn只用于对刚发现的应用终端进行唯一标识，一旦注册从云端分配到devId后，就通过devId来访问该应用终端。
 * 05    model         应用终端型号，同一个厂商下的不同型号设备此值必须不同
 * 06    catID         设备类型ID,取值参见"认证设备类型ID及名称对照表",占三字节（取值范围"000"～"FFF"）
 * 07    vendorID      制造商ID,取值参见"制造商ID及名称对照表"，占三字节（取值范围"000"～ "FFF"）
 * 08    devName       设备名称
 * 09    productId     产品ID
 * 10    version       协议版本，当前版本为1.0
 * 11    mac           ma地址
 * 12    fwv           firmware version.固件版本
 * 13    hwv           hardware version.硬件版本
 * 14    swv           software version.软件版本
 * 15    protType      协议类型
 */
static int OlaBuildRegisterReq(OlaData *data, OlaCoapParam *param)
{
    cJSON *payloadJson = cJSON_CreateObject();
    if (payloadJson == NULL) {
        OlaLogError("create json error");
        return OLA_CJSON_ERR_CREATE_OBJECT;
    }

    char *payload = NULL;
    int ret = OlaAddTicketAndRegistryInfoToJson(payloadJson);
    if (ret != OLA_OK) {
        goto FAILED;
    }

    ret = OlaAddDevinfoToJson(payloadJson);
    if (ret != OLA_OK) {
        goto FAILED;
    }

    payload = cJSON_PrintUnformatted(payloadJson);
    if (payload == NULL) {
        OlaLogError("payload error");
        ret = OLA_CJSON_ERR_PRINT_UNFORMATTED;
        goto FAILED;
    }
    data->data = (unsigned char *)payload;
    data->len = strlen(payload);
    OlaBuildCloudRequestCoapParam(param, OLA_COAP_URI_REGISTER, OlaDeviceRegisterRspHandler, false);
    cJSON_Delete(payloadJson);
    return OLA_OK;
FAILED:
    cJSON_Delete(payloadJson);
    return ret;
}

static int OlaSendRegisterReq(void)
{
    OlaData sendData = { NULL, 0 };
    OlaCoapParam param;
    (void)memset_s(&param, sizeof(OlaCoapParam), 0, sizeof(OlaCoapParam));
    int ret = OlaBuildRegisterReq(&sendData, &param);
    if (ret != OLA_OK) {
        OlaLogWarning("build register request error");
        goto EXIT;
    }

    ret = OlaSendDataToCloud(&sendData, &param);
    if (ret != OLA_OK) {
        OlaLogWarning("send error");
        goto EXIT;
    }

EXIT:
    OlaPutNullAfterFree((void **)&sendData.data);
    return ret;
}

/* 设备注册主流程 */
int OlaRegisterProcess(void)
{
    int ret = OLA_STATE_RUNING;
    int errcode;
    switch (OlaGetRegisterState()) {
        case REGISTER_STATE_SEND_REQUEST:
            OlaNotifySdkEvent(OLA_EVENT_CODE_M2M_REG_START);
            errcode = OlaSendRegisterReq();
            if (errcode == OLA_OK) {
                OlaSetRegisterState(REGISTER_STATE_WAIT_RESPONSE);
                ret = OLA_STATE_RUNING;
            } else {
                OlaLogWarning("send register request error");
                ret = OLA_ERROR;
            }
            break;
        case REGISTER_STATE_WAIT_RESPONSE:
            errcode = OlaWaitCloudResponse(&g_registerTime);
            if (errcode != OLA_OK) {
                OlaLogWarning("wait response error");
                ret = OLA_ERROR;
            }
            break;
        case REGISTER_STATE_FINISH:
            OlaNotifySdkEvent(OLA_EVENT_CODE_M2M_REG_SUCCESS);
            ret = OLA_OK;
            break;
        case REGISTER_STATE_FAILED:
            OlaNotifySdkEvent(OLA_EVENT_CODE_M2M_REG_FAIL);
            ret = OLA_ERROR;
            break;
        default:
            OlaLogWarning("invalid register state");
            ret = OLA_ERROR;
            break;
    }
    return ret;
}

void OlaRegisterInit(void)
{
    OlaSetRegisterState(REGISTER_STATE_SEND_REQUEST);
    g_registerTime = 0;

    /* 初始化内存中保存的注册码及有效时间 */
    (void)memset_s(g_registerCode, sizeof(g_registerCode), 0, sizeof(g_registerCode));
    g_registryCodeTime = 0;
}